#include "process.h"
#include "config.h"
#include "status.h"
#include "memory/memory.h"
#include "task.h"
#include "memory/heap/kheap.h"
#include "fs/file.h"
#include "utils/string.h"
#include "kernel.h"
#include "memory/paging/paging.h"

// The current process that is running
struct process* current_process = 0; 

static struct process* processes[TESTOS_MAX_PROCESSES] = {}; 

static void process_init(struct process* process) 
{
    memset(process, 0, sizeof(struct process)); 


}

struct process* get_current_process()
{
    return current_process; 
}

struct process* process_get(const int pid) 
{
    if (pid < 0 || pid >= TESTOS_MAX_PROCESSES) {
        return 0; 
    }

    return processes[pid]; 
}

static const int process_load_binary(const char* filename, struct process* process)
{
    int res = 0; 
    const int fd = fopen(filename, "r"); 
    if (!fd) {
        res = -TESTOS_STATUS_IO_ERROR; 
        goto out; 
    }

    struct file_stat stat; 
    res = fstat(fd, &stat);
    if (res != TESTOS_STATUS_OK) {
        goto out; 
    }

    void* program_data_ptr = kzalloc(stat.filesize); 
    if (!program_data_ptr) {
        res = -TESTOS_STATUS_NOMEMORY; 
        goto out; 
    }

    if (fread(program_data_ptr, stat.filesize, 1, fd) != 1) {
        // failed to load program data
        res = -TESTOS_STATUS_IO_ERROR;
        goto out; 
    }

    process->ptr = program_data_ptr; 
    process->size = stat.filesize; 

out:
    fclose(fd); 
    return res; 
}

static const int process_load_data(const char* filename, struct process* process)
{
    int res = 0; 
    res = process_load_binary(filename, process);


    return res;
}

const int process_map_binary(struct process* process)
{
    int res = 0;
    void* aalign = paging_align_address(process->ptr + process->size); 
    paging_map_to(
        process->task->page_directory, 
        (void*)TESTOS_PROGRAM_VIRTUAL_ADDRESS, 
        process->ptr, 
        aalign,
        PAGING_IS_PRESENT | PAGING_ACCESS_FROM_ALL | PAGING_IS_WRITEABLE); 

out:
    return res; 
}

const int process_map_memory(struct process* process)
{
    int res = 0; 
    res = process_map_binary(process); 
    if (res < 0) {
        goto out;
    }

    paging_map_to(
        process->task->page_directory, 
        (void*)TESTOS_PROGRAM_VIRTUAL_STACK_ADDRESS_END, 
        process->stack, 
        paging_align_address(process->stack + TESTOS_USER_PROGRAM_STACK_SIZE),
        PAGING_IS_PRESENT | PAGING_ACCESS_FROM_ALL | PAGING_IS_WRITEABLE); 
out:
    return res; 
}

const int process_get_free_slot()
{
    for (int i = 0; i < TESTOS_MAX_PROCESSES; i++) {
        if (processes[0] == 0) {
            return i; 
        }
    }

    return -TESTOS_STATUS_IS_TAKEN; 
}

const int process_load(const char* filename, struct process** process)
{
    int res = 0; 
    const int process_slot = process_get_free_slot(); 
    if (process_slot < 0) {
        res = -TESTOS_STATUS_IS_TAKEN;
        goto out; 
    }

    res = process_load_for_slot(filename, process, process_slot); 

out:
    return res; 
}

const int process_load_for_slot(const char* filename, struct process** process, const int process_slot)
{
    int res = 0; 
    struct task* task = 0; 
    struct process* _process = 0; 
    void* program_stack_ptr = 0; 
    if (process_get(process_slot) != 0) {
        res = -TESTOS_STATUS_IS_TAKEN; 
        goto out; 
    }

    _process = kzalloc(sizeof(struct process)); 
    if (!_process) {
        res = -TESTOS_STATUS_NOMEMORY; 
    }

    process_init(_process); 
    res = process_load_data(filename,  _process); 
    if (res < 0) {
        goto out; 
    }

    program_stack_ptr = kzalloc(TESTOS_USER_PROGRAM_STACK_SIZE); 
    if (!program_stack_ptr) {
        res = TESTOS_STATUS_NOMEMORY; 
        goto out; 
    }

    strncpy(_process->filename, filename, sizeof(_process->filename)); 
    _process->stack = program_stack_ptr; 
    _process->id = process_slot; 

    // Initialize a task
    task = task_new(_process);
    if (ERROR_I(task) == 0) {
        res = ERROR_I(task); 
        goto out; 
    }

    _process->task = task; 

    res = process_map_memory(_process); 
    if (res < 0) {
        goto out; 
    }

    *process = _process; 

    // Add process to the array
    processes[process_slot] = _process; 

out:
    if (ISERR(res)) {
        if (_process) {
            if (_process->task) {
                kfree(_process); 
            }

            task_free(_process->task); 
        }

        if (program_stack_ptr) {
            kfree(program_stack_ptr);
        } 
    }

    return res; 
}